home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / pss < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  49.3 KB  |  1,428 lines

  1. #!/usr/local/bin/gawk -f
  2. #!/usr/bin/awk -f
  3. # @(#) pss.gawk 1.2.1 97/07/23
  4. # 93/05/28 john h. dubois iii (john@armory.com)
  5. # 93/09/30 Added help and -t options.
  6. # 94/03/10 Use gawk so - options can be given.
  7. # 94/07/28 Made TIME field sort right if time > 99:59
  8. # 95/04/10 Added t option.  Changed fieldname to be arg rather than option.
  9. # 95/05/08 Added x option.
  10. # 95/05/19 Added s option.
  11. # 95/08/29 gawk+protlib bug workaround
  12. # 96/01/06 5.0 port: Use new ps options if available.  TTY column is now
  13. #          aligned on the right.
  14. # 96/02/13 Read rcfiles; added oO options.
  15. # 96/05/28 bugfix for above change.
  16. # 96/09/04 Make sort field case insensitive.
  17. # 97/07/23 1.2.1 Make -o- give default fields.
  18.  
  19. # Should use FIELDWIDTHS for this, since that was the original idea...
  20.  
  21. #  1 2      3     4    5     6 7   8   9      10     11     12       13
  22. #  F S      UID   PID  PPID  C PRI NI  ADDR1  ADDR2  SZ     WCHAN    STIME
  23. #  14       15   16
  24. #  TTY      TIME CMD
  25. # 20 S  spcecdt   525     1  0  30 10    563   1089  68  f00fcd64  May 23   07       0:08 -ksh 
  26.  
  27. BEGIN {
  28.     Name = "pss"
  29.     Usage = Name " [-hnctz] [-s<min>] [-[oO]<fields>] <fieldname>"
  30.     SortField = "TIME"
  31.     rcFile = ".pss"
  32.     ARGC = Opts(Name,Usage,"no:czs:tO:hx",0,
  33.     "~/" rcFile ":$HOME/" rcFile ":/etc/default/pss",
  34.     "NONZERO,FIELDS,CPUONLY,SIZE,MIN,TOP,SUPPRESS")
  35. #  F S      UID   PID  PPID  C PRI NI  ADDR1  ADDR2  SZ     WCHAN    STIME  TTY      TIME CMD
  36.     PrintFmt = "UID,PID,PPID,C,PRI,NI,SZ,STIME,TTY,TIME,CMD"
  37.     if ("h" in Options) {
  38.     printf \
  39. "%s: List processes sorted by any field.\n"\
  40. "%s\n"\
  41. "<fieldname> is one of the headers in the output of ps -fl: F, S, UID, PID,\n"\
  42. "PPID, C, PRI, NI, ADDR1, ADDR2, SZ, WCHAN, STIME, TTY, TIME, and CMD.  If\n"\
  43. "given, output is sorted by that field.  The default is TIME (CPU time used\n"\
  44. "by process).\n"\
  45. "Options:\n"\
  46. "Some of the following options can also be set by assigning values to\n"\
  47. "variables in a configuration file.  Three configuration files are read, in\n"\
  48. "order: a file named %s in the invoking user's home directory; a file\n"\
  49. "named %s in the directory specified by the environment variable UHOME\n"\
  50. "(if it is set); and the file /etc/default/pss.  Variables are assigned\n"\
  51. "to with the syntax:  varname=value  or in the case of flags, by simply\n"\
  52. "putting the indicated variable name in the file without a value.\n"\
  53. "A variable assigned to in one of these files will override values assigned\n"\
  54. "to the same variable in one of the files read after it.  To turn off an\n"\
  55. "option and prevent it from being set in a file read later, assign it a\n"\
  56. "value of 0.  e.g. if CPUONLY is set in /etc/default/pss, CPUONLY=0 in\n"\
  57. "a %s file will override it.  Flag options can be turned off on the\n"\
  58. "command line by following them immediately with '-', e.g. -z- to turn off\n"\
  59. "the z option in such a way that it cannot be turned on in a config file.\n"\
  60. "Variable names appear in parentheses in the option descriptions.\n"\
  61. "-h: Print this help.\n"\
  62. "-n: Only processes with a non-zero sort field are printed.  With the\n"\
  63. "    default sort field, this produces a listing of processes that have\n"\
  64. "    used one second or more of CPU time.  (NONZERO)\n"\
  65. "-o<fields>: Make the output consist of the fields given in the comma-\n"\
  66. "    seperated list.  The valid field names are the same as those that can\n"\
  67. "    be given as a sort field.  If FIELDS is set, -o- can be used to\n"\
  68. "    get the default fields.  The default fields are:\n"\
  69. "    %s  (FIELDS)\n"\
  70. "-O<fields>: Do not print the named fields.  (SUPPRESS)\n"\
  71. "-c: List cpu-using processes only.  Equivalent to '-n C'.  (CPUONLY)\n"\
  72. "-z: Sort by size field (equivalent to a <fieldname> of SZ).  (SIZE)\n"\
  73. "-s<min>: List only processes whose sort field is equal to or greater than\n"\
  74. "    <min>.  Values are compared numerically.  (MIN)\n"\
  75. "-t: Print top processes only.  Output lines are truncated to the screen\n"\
  76. "    width, and only as many as will fit on the screen are printed.  (TOP)\n",
  77. Name,Usage,rcFile,rcFile,rcFile,PrintFmt
  78.     exit(0)
  79.     }
  80.     Debug = "x" in Options
  81.     if (ARGC > 2) {
  82.     printf "%s: too many arguments.  Use -h for help.\n",Name \
  83.     > "/dev/stderr"
  84.     exit 1
  85.     }
  86.     # Make fd 0 be /dev/null to work around gawk+protlib bugs
  87.     # Existance of /stand is a fast if imperfect test for OS >= 5.0
  88.     # Use new PS options if available, for flexibility in the future
  89.     Cmd = "[ ! -d /stand ] && exec ps -efl < /dev/null || "\
  90.     "exec ps -e -of -os -ouser -opid -oppid -oc -opri -onice -oaddr "\
  91.     "-ovsz -owchan -ostime -otty -otime -oargs </dev/null"
  92.     Cmd | getline Header
  93.     if (Debug)
  94.     printf "Header line:\n%s\n",Header
  95.     NewPS = Header ~ "VSZ"
  96.     # tty might be 'console'
  97.     FWidths = SetFields(Header,0,(NewPS ? "" : "TTY=7 ") "CMD=0 COMMAND=0",
  98.     Fields,Gutter)
  99.  
  100.     if ("z" in Options)
  101.     SortField = "SZ"
  102.     if (SortField == "SZ")
  103.     Numeric = 1    # should really have this for other fields too
  104.     else
  105.     Numeric = 0
  106.     if (NewPS) {
  107.     if (SortField == "SZ")
  108.         SortField = "VSZ"
  109.     else if (SortField == "UID")
  110.         SortField = "USER"
  111.     else if (SortField == "CMD")
  112.         SortField = "COMMAND"
  113.     }
  114.     if ("t" in Options)
  115.     HeadTailInit()
  116.     if (ARGC == 2)
  117.     SortField = toupper(ARGV[1])
  118.     for (Elem in Fields)
  119.     AllFields[Fields[Elem]]
  120.     if (!(SortField in AllFields)) {
  121.     printf "%s: %s: No such field.\n",Name,SortField > "/dev/stderr"
  122.     exit 1
  123.     }
  124.     Nonzero = "n" in Options
  125.     if ("c" in Options) {
  126.     Nonzero = 1
  127.     SortField = "C"
  128.     }
  129.     if (Debug)
  130.     printf "Sort field is <%s>\n",SortField > "/dev/stderr"
  131.  
  132.     if ("o" in Options && Options["o"] != "-")
  133.     PrintFmt = toupper(Options["o"])
  134.     NumFmt = split(PrintFmt,Format,",")
  135.     if (Debug)
  136.     printf "Initially %d fields: %s\n",NumFmt,PrintFmt
  137.     if ("O" in Options) {
  138.     split(toupper(Options["O"]),Suppress,",")
  139.     for (i in Suppress) {
  140.         opt = Suppress[i]
  141.         if (!(opt in AllFields)) {
  142.         printf "Bad field given with -O: %s.  Exiting.\n",
  143.         opt > "/dev/stderr"
  144.         exit 1
  145.         }
  146.         for (j in Format)
  147.         if (opt == Format[j]) {
  148.             if (Debug)
  149.             printf "Suppressing field %s\n",opt > "/dev/stderr"
  150.             delete Format[j]
  151.             NumFmt--
  152.             break
  153.         }
  154.     }
  155.     }
  156.     PackArr(Format,NumFmt,1)
  157.     if (Debug) {
  158.     print "Fields are now:" > "/dev/stderr"
  159.     for (i = 1; i in Format; i++)
  160.         printf "%d: %s\n",i,Format[i]
  161.     }
  162.     LineNum = 0
  163.     split(FWidths,Widths)
  164.     GutterSort = SortField == "TIME"
  165.     if (Debug)
  166.     print "Command: " Cmd
  167.     FS = " +"
  168.     while ((ret = (Cmd | getline)) == 1) {
  169.     if (Debug)
  170.         printf "."
  171.     FieldsByName(Fields,FieldVals,$0,Widths)
  172.     if (NewPS)
  173.         MapNames(FieldVals)
  174.     Lines[++LineNum] = MakeLine(Format,FieldVals)
  175.     SortVal = sprintf("%6s",FieldVals[SortField])
  176.     # Special case for TIME field.  If > 99:59 minutes it will exceed its
  177.     # field with and overflow into the gutter that follows it.  So, to
  178.     # make it sort right, if it *hasn't* done that move the gutter space
  179.     # to the start of the field.
  180.     # its field width
  181.     if (GutterSort && SortVal ~ / $/) {
  182.         sub(/ $/,"",SortVal)
  183.         SortVal = " " SortVal
  184.     }
  185.     if (Numeric)
  186.         SortVal += 0
  187.     SortInd[LineNum] = SortVal
  188.     }
  189.     if (Debug)
  190.     print ""
  191.     if (estat = close(Cmd))
  192.     printf "%s: %s exited with status %d\n",Name,Cmd,estat
  193.     if (ret) {
  194.     print "Error reading output of '%s': %s\n",Cmd,ERRNO
  195.     exit 1
  196.     }
  197.  
  198.     if (Debug)
  199.     printf "%d line(s) to process.\n",LineNum
  200.  
  201.     # Lines[n] contains the formatted output lines.
  202.     # SortInd[n] contains the sort field.
  203.  
  204.     # Get rid of unwanted output here to reduce sorting work.
  205.     if ("s" in Options) {
  206.     Min = Options["s"]
  207.     gsub("[^0-9a-f]","",Min)    # discard non-num chars
  208.     Min += 0    # force to num val
  209.     for (i = 1; i <= LineNum; i++) {
  210.         Ind = SortInd[i]
  211.         gsub("[^0-9a-f]","",Ind)    # discard non-num chars
  212.         if (Ind+0 < Min) {
  213.         if (Debug)
  214.             printf "%s < %s; discarding %s\n",Ind,Min,Lines[i]
  215.         delete SortInd[i]
  216.         }
  217.     }
  218.     }
  219.     # Can't use qsortNumIndByValue because SortInd may be missing some indices
  220.     LineNum = qsortArbIndByValue(SortInd,k)
  221.     if (Debug)
  222.     printf "%d line(s) to print.\n",LineNum
  223.  
  224.     # Print header
  225.     FieldsByName(Fields,FieldVals,Header,Widths)
  226.     if (NewPS)
  227.     MapNames(FieldVals)
  228.     HeadPrint(MakeLine(Format,FieldVals))
  229.  
  230.     for (i = LineNum; i >= 1; i--) {
  231.     # Stop when a zero value is reached (one that has no non-0 digits in it)
  232.     if (Nonzero && SortInd[k[i]] !~ "[1-9a-f]")
  233.         exit
  234.     if (!HeadPrint(Lines[k[i]])) {
  235.         if (Debug)
  236.         print "Finished printing top lines." > "/dev/stderr"
  237.         exit 0
  238.     }
  239. #    else if (Debug)
  240. #        printf "Printed line %d\n",i
  241.     }
  242. }
  243.  
  244. function MapNames(FieldVals) {
  245.     if ("COMMAND" in FieldVals)
  246.     FieldVals["CMD"] = FieldVals["COMMAND"]
  247.     if ("USER" in FieldVals) {
  248.     FieldVals["UID"] = FieldVals["USER"]
  249.     }
  250.     if ("VSZ" in FieldVals)
  251.     FieldVals["SZ"] = FieldVals["VSZ"]
  252. }
  253.  
  254. # @(#) headtail.awk 95/06/20
  255. # 95/04/28 Added tail routines.
  256.  
  257. # Turn on screen-bounded printing.
  258. # Sets global vars LINES and COLUMNS.
  259. # Set either of them to 0 after calling this function if you do not want
  260. # limiting of lines or line length respectively.
  261. function HeadTailInit(  Cmd) {
  262.     # tput will use values in environment, but we want to avoid running
  263.     # it if possible.
  264.     if ("COLUMNS" in ENVIRON)
  265.     COLUMNS = ENVIRON["COLUMNS"]
  266.     else {
  267.     Cmd = "exec tput cols"
  268.     Cmd | getline COLUMNS
  269.     close(Cmd)
  270.     if (COLUMNS == "")
  271.         COLUMNS = 80
  272.     }
  273.     if ("LINES" in ENVIRON)
  274.     LINES = ENVIRON["LINES"]
  275.     else {
  276.     Cmd = "exec tput lines"
  277.     Cmd | getline LINES
  278.     close(Cmd)
  279.     if (LINES == "")
  280.         LINES = 24
  281.     }
  282. }
  283.  
  284. # Do screen-bound printing.  
  285. # If LINES  is >0, the last LINES-1 lines are kept in a circular buffer.  
  286. # When TailFlush() is called, they are printed.
  287. # If LINES = 0, all lines are printed immediately.
  288. # If COLUMNS is >0, truncates Line to COLUMNS-1 characters before printing it.
  289. # Global vars: uses LINES & COLUMNS; sets/uses TailPtr;
  290. # saves lines in TailLines[] from 1..LINES-1
  291. # Embedded newlines split the line into multiple lines; trailing newlines are
  292. # stripped.  Tabs are expanded to spaces.
  293. function TailPrint(Line) {
  294.     if (!LINES)
  295.     print Line
  296.     else {
  297.     if (++TailPtr > (LINES-1))
  298.         TailPtr = 1
  299.     TailLines[TailPtr] = Line
  300.     }
  301. }
  302.  
  303. function TailFlush(  NumPrinted,Lines,Line,i,Buffer,PrintLines) {
  304.     if (!LINES)
  305.     return
  306.     NumPrinted = 0
  307.     PrintLines = LINES-1
  308.     # Since lines may contain multiple lines, we must create a buffer to be
  309.     # printed by reading line buffer backwards.
  310.     # Stop when we've copied enough lines, or if we wrap around to the end and
  311.     # find that the entire line buffer wasn't used.
  312.     while (NumPrinted < PrintLines && TailPtr in TailLines) {
  313.     # Split line into individual lines, then process them last to first
  314.     Num = split(TailLines[TailPtr],Lines,"\n")
  315.     for (i = Num; i >= 1; i--) {
  316.         Line = Lines[i]
  317.         if (i == Num && Line == "")    # discard trailing newline
  318.         continue
  319.         # Put this line at the front of the print buffer
  320.         if (COLUMNS)
  321.         Buffer = substr(TabEx(Line),1,COLUMNS - 1) "\n" Buffer
  322.         else
  323.         Buffer = Line "\n" Buffer
  324.         if (++NumPrinted == PrintLines)
  325.         break
  326.     }
  327.     if (!--TailPtr)    # Wrap pointer if neccessary
  328.         TailPtr = PrintLines
  329.     }
  330.     printf "%s",Buffer
  331. }
  332.  
  333. # Do screen-bound printing.  
  334. # If LINES >0, returns 0 when LINES-1 lines have been printed by HeadPrint().
  335. # Otherwise returns 1.
  336. # If COLUMNS is >0, truncates Line to COLUMNS-1 characters before printing it.
  337. # Global vars: uses LINES & COLUMNS; sets/uses LinesPrinted.
  338. # Line should not include newlines.
  339. function HeadPrint(Line) {
  340.     # Check first, in case some calls of this function to not check return
  341.     # value, and in case LINES is 1.
  342.     if (LINES && LinesPrinted >= (LINES-1))
  343.     return 0
  344.     if (COLUMNS)
  345.     print substr(Line,1,COLUMNS - 1)
  346.     else
  347.     print Line
  348.     if (LINES && ++LinesPrinted >= (LINES-1))
  349.     return 0
  350.     return 1
  351. }
  352.  
  353. # Expand tabs in Line
  354. function TabEx(Line,  Segs,i,Num,S) {
  355.     Num = split(Line,Segs,"\t")
  356.     Line = ""
  357.     for (i = 1; i < Num; i++) {
  358.     S = Segs[i]
  359.     Line = Line S substr("        ",length(S) % 8 + 1)
  360.     }
  361.     return Line Segs[Num]
  362. }
  363.  
  364. function MakeLine(Format,Fields,  Line,Name) {
  365.     for (i = 1; i in Format; i++) {
  366.     Name = Format[i]
  367.     if (Name in Fields)
  368.         Line = Line Fields[Name]
  369.     else
  370.         printf "Error: MakeLine(): no such field '%s' in Fields[].\n",
  371.         Name > "/dev/stderr"
  372.     }
  373.     return Line
  374. }
  375.  
  376. # FieldsByName: Put fields in FieldVals[] indexed by their names as given in
  377. # FieldNames[].
  378. # Line is the input line to split up.
  379. # Widths gives the width of each field.
  380. # FieldNames contains an index for each field name that is to be printed.
  381. function FieldsByName(FieldNames,FieldVals,Line,Widths,  i,Pos) {
  382.     Pos = 1
  383.     for (i = 1; i in Widths; i++) {
  384.     if (i in FieldNames)
  385.         FieldVals[FieldNames[i]] = substr(Line,Pos,Widths[i])
  386.     Pos += Widths[i]
  387.     }
  388. }
  389.  
  390. # SetFields: generate a FIELDWIDTHS string and a field name map from a header.
  391. # SetFields parses a header and uses it to generate a list of field widths.
  392. # Input vars:
  393. # Header is the header to parse.  It should contain field names separted by
  394. # whitespace.
  395. # LeftAdj sets the default field name adjustment.
  396. # If 1, field names are processed as left adjusted by default.
  397. # This means that the columns corresponding to the spaces after a field
  398. # name are given to the field named on their left.
  399. # If LeftAdj is 0, field names are processed as right adjusted by default;
  400. # columns corresponding to the spaces after a field name are given to the
  401. # field named on their right.
  402. # AltAdjFields is a list of fields which have adjustment opposite the default.
  403. # It has the form "fieldname1=width fieldname2=width ..."
  404. # Each width specifies the width of the named field.
  405. # The width must be given so that the columns between a left-adjusted and a
  406. # right-adjusted field can be assigned.  A width of 0 means the field is the
  407. # rightmost field, it is left-adjusted, and it has no fixed length.
  408. # Output vars:
  409. # Fields[] gives the names of the fields, indexed by the field numbers as
  410. # they will be set by gawk when FIELDWIDTHS is set.
  411. # Gutter[fieldname] is set to 1 if a gutter was added to the field.
  412. # Return value:
  413. # The return value is a FIELDWIDTHS string.
  414. # Example: 
  415. # For parsing ps output in various formats:
  416. # FIELDWIDTHS = \
  417. # SetFields(Header,0,"TTY=4 CMD=0 COMMAND=0",Fields)
  418. function SetFields(Header,LeftAdj,AltAdjFields,Fields,Gutter,
  419. Offset,NFields,HFields,Pos,Alt,i,Name,Adj,Lengths,PreSpace,Columns,
  420. OtherField,FieldNum,Widths) {
  421.     Offset = 1-LeftAdj*2
  422.     NFields = split(Header,HFields)
  423.     Pos = 0
  424.     Assign(Alt,AltAdjFields," +","=")
  425.     # This loop sets the following:
  426.     # Adj[i]: whether field i is left-adjusted
  427.     # Lengths[i]: Length of field name.
  428.     # PreSpace[i]: Number of spaces in header before field name.
  429.     for (i = 1; i <= NFields; i++) {
  430.     Name = HFields[i]
  431.     if (Name in Alt)
  432.         Adj[i] = !LeftAdj
  433.     else
  434.         Adj[i] = LeftAdj
  435.     Lengths[i] = length(Name)
  436.     PreSpace[i] = index(Header,Name) - 1
  437.     # Strip off the field name just processed
  438.     Header = substr(Header,Lengths[i] + PreSpace[i] + 1)
  439.     }
  440.     for (i = 1; i <= NFields; i++) {
  441.     # The columns that go to this field by default
  442.     Columns = PreSpace[i+Adj[i]] 
  443.     # The field on the other side of the columns
  444.     OtherField = i + Adj[i]*2-1
  445.     if (Adj[i] == Adj[OtherField])
  446.         # If this field and the other both have the same adjustment,
  447.         # then this field gets the columns.
  448.         Lengths[i] += Columns
  449.     else {
  450.         Name = HFields[i]
  451.         # If this field and the other have different adjustment, then one
  452.         # or the other other (but not both) of them is in Alt[].
  453.         # If it's this field, set its width to the value given in
  454.         # Alt and give whatever is left to the other.
  455.         if (Name in Alt) {
  456.         if (Alt[Name] == 0)
  457.             Alt[Name] = 1000000
  458.         Lengths[OtherField] += Lengths[i] + Columns - Alt[Name]
  459.         Lengths[i] = Alt[Name]
  460.         }
  461.     }
  462.     }
  463.     FieldNum = 0
  464.     for (i = 1; i <= NFields; i++) {
  465.     Name = HFields[i]
  466.     Fields[++FieldNum] = Name
  467.     # If this field is right adjusted and next is left adjusted,
  468.     # there is an unused gap between them.  Give one char of it
  469.     # to the left field so there will be a gutter in the output.
  470.     if (!Adj[i] && Adj[i+1]) {
  471.         Widths = Widths Lengths[i]+1 " "
  472.         # For anything that cares that a gutter is part of this field
  473.         Gutter[Name] = 1
  474.         if (PreSpace[i+1] > 1) {
  475.         Widths = Widths PreSpace[i+1]-1 " "
  476.         ++FieldNum
  477.         }
  478.     }
  479.     else
  480.         Widths = Widths Lengths[i] " "
  481.     }
  482.     return Widths
  483. }
  484.  
  485. ### Begin qsort routines
  486.  
  487. # Arr[] is an array of values with arbitrary indices.
  488. # k[] is returned with numeric indices 1..n.
  489. # The values in k[] are the indices of Arr[],
  490. # ordered so that if Arr[] is stepped through
  491. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  492. # through in order of the values of its elements.
  493. # The return value is the number of elements in the arrays (n).
  494. function qsortArbIndByValue(Arr,k,  ArrInd,ElNum) {
  495.     ElNum = 0
  496.     for (ArrInd in Arr)
  497.     k[++ElNum] = ArrInd
  498.     qsortSegment(Arr,k,1,ElNum)
  499.     return ElNum
  500. }
  501.  
  502. # Sort a segment of an array.
  503. # Arr[] contains data with arbitrary indices.
  504. # k[] has indices 1..nelem, with the indices of arr[] as values.
  505. # This function sorts the elements of arr that are pointed to by
  506. # k[start..end], swapping the values of elements of k[] so that
  507. # when this function returns arr[k[start..end]] will be in order.
  508. function qsortSegment(Arr,k,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  509.     # handle two-element case explicitly for a tiny speedup
  510.     if ((end - start) == 1) {
  511.     if (Arr[tmps = k[start]] > Arr[tmpe = k[end]]) {
  512.         k[start] = tmpe
  513.         k[end] = tmps
  514.     }
  515.     return
  516.     }
  517.     # Make sure comparisons act on these as numbers
  518.     left = start+0
  519.     right = end+0
  520.     sepval = Arr[k[int((left + right) / 2)]]
  521.     # Make every element <= sepval be to the left of every element > sepval
  522.     while (left < right) {
  523.     while (Arr[k[left]] < sepval)
  524.         left++
  525.     while (Arr[k[right]] > sepval)
  526.         right--
  527.     if (left < right) {
  528.         tmp = k[left]
  529.         k[left++] = k[right]
  530.         k[right--] = tmp
  531.     }
  532.     }
  533.     if (left == right)
  534.     if (Arr[k[left]] < sepval)
  535.         left++
  536.     else
  537.         right--
  538.     if (start < right)
  539.     qsortSegment(Arr,k,start,right)
  540.     if (left < end)
  541.     qsortSegment(Arr,k,left,end)
  542. }
  543.  
  544. # Arr[] is an array of values with arbitrary indices.
  545. # k[] is returned with numeric indices 1..n.
  546. # The values in k are the indices of Arr[],
  547. # ordered so that if Arr[] is stepped through
  548. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  549. # through in order of the values of its indices.
  550. # The return value is the number of elements in the arrays (n).
  551. # If the indexes are numeric, Numeric should be true, so that they can be
  552. # compared as such rather than as strings.  Numeric indexes do not have to be
  553. # contiguous.
  554. function qsortByArbIndex(Arr,k,Numeric,  ArrInd,ElNum) {
  555.     ElNum = 0
  556.     if (Numeric)
  557.     # Indexes do not preserve numeric type, so must be forced
  558.     for (ArrInd in Arr)
  559.         k[++ElNum] = ArrInd+0
  560.     else
  561.     for (ArrInd in Arr)
  562.         k[++ElNum] = ArrInd
  563.     qsortNumIndByValue(k,1,ElNum)
  564.     return ElNum
  565. }
  566.  
  567. # Arr is an array of elements with contiguous numeric indexes to be sorted
  568. # by value.
  569. # start and end are the starting and ending indexes of the range to be sorted.
  570. function qsortNumIndByValue(Arr,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  571.     # handle two-element case explicitly for a tiny speedup
  572.     if ((start - end) == 1) {
  573.     if ((tmps = Arr[start]) > (tmpe = Arr[end])) {
  574.         Arr[start] = tmpe
  575.         Arr[end] = tmps
  576.     }
  577.     return
  578.     }
  579.     left = start+0
  580.     right = end+0
  581.     sepval = Arr[int((left + right) / 2)]
  582.     while (left < right) {
  583.     while (Arr[left] < sepval)
  584.         left++
  585.     while (Arr[right] > sepval)
  586.         right--
  587.     if (left <= right) {
  588.         tmp = Arr[left]
  589.         Arr[left++] = Arr[right]
  590.         Arr[right--] = tmp
  591.     }
  592.     }
  593.     if (start < right)
  594.     qsortNumIndByValue(Arr,start,right)
  595.     if (left < end)
  596.     qsortNumIndByValue(Arr,left,end)
  597. }
  598.  
  599. ### End qsort routines
  600.  
  601. ### Begin set library
  602.  
  603. function Intersection(A,B,Inter,  Elem,Count) {
  604.     for (Elem in A)
  605.     if (Elem in B) {
  606.         Inter[Elem]
  607.         Count++
  608.     }
  609.     return Count
  610. }
  611.  
  612. function Union(A,B,Both,  Elem) {
  613.     for (Elem in A)
  614.     Both[Elem]
  615.     for (Elem in B)
  616.     Both[Elem]
  617. }
  618.  
  619. # Deletes any elements that are in both Minuend and Subtrahend from Minuend.
  620. function SubtractSet(Minuend,Subtrahend,  Elem) {
  621.     for (Elem in Subtrahend)
  622.     delete Minuend[Elem]
  623. }
  624.  
  625. function CopySet(From,To,  Elem) {
  626.     for (Elem in From)
  627.     To[Elem]
  628. }
  629.  
  630. # Returns 1 if Set is empty, 0 if not.
  631. function IsEmpty(Set,  i) {
  632.     for (i in Set)
  633.     return 0
  634.     return 1
  635. }
  636.  
  637. # MakeSet: make a set from a list.
  638. # An index with the name of each element of the list
  639. # is created in the given array.
  640. # Input variables: 
  641. # Elements is a string containing the list of elements.
  642. # Sep is the character that separates the elements of the list.
  643. # Output variables:
  644. # Set is the array.
  645. # Return value: the number of elements added to the set.
  646. function MakeSet(Set,Elements,Sep,  i,Num,Names) {
  647.     Num = split(Elements,Names,Sep)
  648.     for (i = 1; i <= Num; i++)
  649.     Set[Names[i]]
  650.     return Num
  651. }
  652. # Returns the number of elements in set Set
  653. function NumElem(Set,  elem,Num) {
  654.     for (elem in Set)
  655.     Num++
  656.     return Num
  657. }
  658.  
  659. # Remove all elements from Set
  660. function DeleteAll(Set,  i) {
  661.     for (i in Set)
  662.     delete Set[i]
  663. }
  664.  
  665. ### End set library
  666. ### Start of ProcArgs library
  667. # @(#) ProcArgs 1.11 96/12/08
  668. # 92/02/29 john h. dubois iii (john@armory.com)
  669. # 93/07/18 Added "#" arg type
  670. # 93/09/26 Do not count -h against MinArgs
  671. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  672. #          Removed meaning of "+" or "-" by itself.
  673. # 94/03/08 Added & option and *()< option types.
  674. # 94/04/02 Added NoRCopt to Opts()
  675. # 94/06/11 Mark numeric variables as such.
  676. # 94/07/08 Opts(): Do not require any args if h option is given.
  677. # 95/01/22 Record options given more than once.  Record option num in argv.
  678. # 95/06/08 Added ExclusiveOptions().
  679. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  680. #          Expand $VARNAME at the start of its filenames.
  681. #          Let varname=0 and -option- turn off an option.
  682. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  683. #          of the vars should be searched for in the environment.
  684. #          Check for duplicate rcfiles.
  685. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  686. #          now return various negatives values on error, not just -1, and
  687. #          Opts() may set Err to various positive values, not just 1.
  688. #          Added AllowUnrecOpt.
  689. # 96/05/23 Check type given for & option
  690. # 96/06/15 Re-port to awk
  691. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  692. #          used by other functions.
  693. # 96/10/15 Added OptChars
  694. # 96/11/01 Added exOpts arg to Opts()
  695. # 96/11/16 Added ; type
  696. # 96/12/08 Added Opt2Set() & Opt2Sets()
  697. # 96/12/27 Added CmdLineOpt()
  698.  
  699. # optlist is a string which contains all of the possible command line options.
  700. # A character followed by certain characters indicates that the option takes
  701. # an argument, with type as follows:
  702. # :    String argument
  703. # ;    Non-empty string argument
  704. # *    Floating point argument
  705. # (    Non-negative floating point argument
  706. # )    Positive floating point argument
  707. # #    Integer argument
  708. # <    Non-negative integer argument
  709. # >    Positive integer argument
  710. # The only difference the type of argument makes is in the runtime argument
  711. # error checking that is done.
  712.  
  713. # The & option is a special case used to get numeric options without the
  714. # user having to give an option character.  It is shorthand for [-+.0-9].
  715. # If & is included in optlist and an option string that begins with one of
  716. # these characters is seen, the value given to "&" will include the first
  717. # char of the option.  & must be followed by a type character other than ":"
  718. # or ";".
  719. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  720.  
  721. # Strings in argv[] which begin with "-" or "+" are taken to be
  722. # strings of options, except that a string which consists solely of "-"
  723. # or "+" is taken to be a non-option string; like other non-option strings,
  724. # it stops the scanning of argv and is left in argv[].
  725. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  726. # If an option takes an argument, the argument may either immediately
  727. # follow it or be given separately.
  728. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  729. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  730. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  731. # this feature had a flaw that caused problems in some cases.  See the OptChars
  732. # parameter to explicitly set the option-specifier characters.
  733.  
  734. # If an option that does not take an argument is given,
  735. # an index with its name is created in Options and its value is set to the
  736. # number of times it occurs in argv[].
  737.  
  738. # If an option that does take an argument is given, an index with its name is
  739. # created in Options and its value is set to the value of the argument given
  740. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  741. # If an option that takes an argument is given more than once,
  742. # Options[option-name,"count"] is incremented, and the value is assigned to
  743. # the index (option-name,instance) where instance is 2 for the second occurance
  744. # of the option, etc.
  745. # In other words, the first time an option with a value is encountered, the
  746. # value is assigned to an index consisting only of its name; for any further
  747. # occurances of the option, the value index has an extra (count) dimension.
  748.  
  749. # The sequence number for each option found in argv[] is stored in
  750. # Options[option-name,"num",instance], where instance is 1 for the first
  751. # occurance of the option, etc.  The sequence number starts at 1 and is
  752. # incremented for each option, both those that have a value and those that
  753. # do not.  Options set from a config file have a value of 0 assigned to this.
  754.  
  755. # Options and their arguments are deleted from argv.
  756. # Note that this means that there may be gaps left in the indices of argv[].
  757. # If compress is nonzero, argv[] is packed by moving its elements so that
  758. # they have contiguous integer indices starting with 0.
  759. # Option processing will stop with the first unrecognized option, just as
  760. # though -- was given except that unlike -- the unrecognized option will not be
  761. # removed from ARGV[].  Normally, an error value is returned in this case.
  762. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  763. # be found, so the number of remaining arguments is returned instead.
  764. # If OptChars is not a null string, it is the set of characters that indicate
  765. # that an argument is an option string if the string begins with one of the
  766. # characters.  A string consisting solely of two of the same option-indicator
  767. # characters stops the scanning of argv[].  The default is "-+".
  768. # argv[0] is not examined.
  769. # The number of arguments left in argc is returned.
  770. # If an error occurs, the global string OptErr is set to an error message
  771. # and a negative value is returned.
  772. # Current error values:
  773. # -1: option that required an argument did not get it.
  774. # -2: argument of incorrect type supplied for an option.
  775. # -3: unrecognized (invalid) option.
  776. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  777. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  778. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  779. {
  780. # ArgNum is the index of the argument being processed.
  781. # ArgsLeft is the number of arguments left in argv.
  782. # Arg is the argument being processed.
  783. # ArgLen is the length of the argument being processed.
  784. # ArgInd is the position of the character in Arg being processed.
  785. # Option is the character in Arg being processed.
  786. # Pos is the position in OptList of the option being processed.
  787. # NumOpt is true if a numeric option may be given.
  788.     ArgsLeft = argc
  789.     NumOpt = index(OptList,"&")
  790.     OptionNum = 0
  791.     if (OptChars == "")
  792.     OptChars = "-+"
  793.     while (OptChars != "") {
  794.     c = substr(OptChars,1,1)
  795.     OptChars = substr(OptChars,2)
  796.     OptCharSet[c]
  797.     OptTerm[c c]
  798.     }
  799.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  800.     Arg = argv[ArgNum]
  801.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  802.         break    # Not an option; quit
  803.     if (Arg in OptTerm) {
  804.         delete argv[ArgNum]
  805.         ArgsLeft--
  806.         break
  807.     }
  808.     ArgLen = length(Arg)
  809.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  810.         Option = substr(Arg,ArgInd,1)
  811.         if (NumOpt && Option ~ /[-+.0-9]/) {
  812.         # If this option is a numeric option, make its flag be & and
  813.         # its option string flag position be the position of & in
  814.         # the option string.
  815.         Option = "&"
  816.         Pos = NumOpt
  817.         # Prefix Arg with a char so that ArgInd will point to the
  818.         # first char of the numeric option.
  819.         Arg = "&" Arg
  820.         ArgLen++
  821.         }
  822.         # Find position of flag in option string, to get its type (if any).
  823.         # Disallow & as literal flag.
  824.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  825.         if (AllowUnrecOpt) {
  826.             Escape = 1
  827.             break
  828.         }
  829.         else {
  830.             OptErr = "Invalid option: " specGiven Option
  831.             return -3
  832.         }
  833.         }
  834.  
  835.         # Find what the value of the option will be if it takes one.
  836.         # NeedNextOpt is true if the option specifier is the last char of
  837.         # this arg, which means that if the option requires a value it is
  838.         # the next arg.
  839.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  840.         if (GotValue = ArgNum + 1 < argc)
  841.             Value = argv[ArgNum+1]
  842.         }
  843.         else {    # Value is included with option
  844.         Value = substr(Arg,ArgInd + 1)
  845.         GotValue = 1
  846.         }
  847.  
  848.         if (HadValue = AssignVal(Option,Value,Options,
  849.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  850.         specGiven)) {
  851.         if (HadValue < 0)    # error occured
  852.             return HadValue
  853.         if (HadValue == 2)
  854.             ArgInd++    # Account for the single-char value we used.
  855.         else {
  856.             if (NeedNextOpt) {    # option took next arg as value
  857.             delete argv[++ArgNum]
  858.             ArgsLeft--
  859.             }
  860.             break    # This option has been used up
  861.         }
  862.         }
  863.     }
  864.     if (Escape)
  865.         break
  866.     # Do not delete arg until after processing of it, so that if it is not
  867.     # recognized it can be left in ARGV[].
  868.     delete argv[ArgNum]
  869.     ArgsLeft--
  870.     }
  871.     if (compress != 0) {
  872.     dest = 1
  873.     src = argc - ArgsLeft + 1
  874.     for (count = ArgsLeft - 1; count; count--) {
  875.         ARGV[dest] = ARGV[src]
  876.         dest++
  877.         src++
  878.     }
  879.     }
  880.     return ArgsLeft
  881. }
  882.  
  883. # Assignment to values in Options[] occurs only in this function.
  884. # Option: Option specifier character.
  885. # Value: Value to be assigned to option, if it takes a value.
  886. # Options[]: Options array to return values in.
  887. # ArgType: Argument type specifier character.
  888. # GotValue: Whether any value is available to be assigned to this option.
  889. # Name: Name of option being processed.
  890. # OptionNum: Number of this option (starting with 1) if set in argv[],
  891. #     or 0 if it was given in a config file or in the environment.
  892. # SingleOpt: true if the value (if any) that is available for this option was
  893. #     given as part of the same command line arg as the option.  Used only for
  894. #     options from the command line.
  895. # specGiven is the option specifier character use, if any (e.g. - or +),
  896. # for use in error messages.
  897. # Global variables: OptErr
  898. # Return value: negative value on error, 0 if option did not require an
  899. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  900. # the arg.
  901. # Current error values:
  902. # -1: Option that required an argument did not get it.
  903. # -2: Value of incorrect type supplied for option.
  904. # -3: Bad type given for option &
  905. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  906. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  907.     # If option takes a value...    [
  908.     NumTypes = "*()#<>]"
  909.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  910.     OptErr = "Bad type given for & option"
  911.     return -3
  912.     }
  913.  
  914.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  915.     if (!GotValue) {
  916.         if (Name != "")
  917.         OptErr = "Variable requires a value -- " Name
  918.         else
  919.         OptErr = "option requires an argument -- " Option
  920.         return -1
  921.     }
  922.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  923.         OptErr = Err
  924.         return -2
  925.     }
  926.     # Mark this as a numeric variable; will be propogated to Options[] val.
  927.     if (ArgType != ":" && ArgType != ";")
  928.         Value += 0
  929.     if ((Instance = ++Options[Option,"count"]) > 1)
  930.         Options[Option,Instance] = Value
  931.     else
  932.         Options[Option] = Value
  933.     }
  934.     # If this is an environ or rcfile assignment & it was given a value...
  935.     else if (!OptionNum && Value != "") {
  936.     UsedValue = 1
  937.     # If the value is "0" or "-" and this is the first instance of it,
  938.     # do not set Options[Option]; this allows an assignment in an rcfile to
  939.     # turn off an option (for the simple "Option in Options" test) in such
  940.     # a way that it cannot be turned on in a later file.
  941.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  942.         Instance = 1
  943.     else
  944.         Instance = ++Options[Option]
  945.     # Save the value even though this is a flag
  946.     Options[Option,Instance] = Value
  947.     }
  948.     # If this is a command line flag and has a - following it in the same arg,
  949.     # it is being turned off.
  950.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  951.     UsedValue = 2
  952.     if (Option in Options)
  953.         Instance = ++Options[Option]
  954.     else
  955.         Instance = 1
  956.     Options[Option,Instance]
  957.     }
  958.     # If this is a flag assignment without a value, increment the count for the
  959.     # flag unless it was turned off.  The indicator for a flag being turned off
  960.     # is that the flag index has not been set in Options[] but it has an
  961.     # instance count.
  962.     else if (Option in Options || !((Option,1) in Options))
  963.     # Increment number of times this flag seen; will inc null value to 1
  964.     Instance = ++Options[Option]
  965.     Options[Option,"num",Instance] = OptionNum
  966.     return UsedValue
  967. }
  968.  
  969. # Option is the option letter
  970. # Value is the value being assigned
  971. # Name is the var name of the option, if any
  972. # ArgType is one of:
  973. # :    String argument
  974. # ;    Non-null string argument
  975. # *    Floating point argument
  976. # (    Non-negative floating point argument
  977. # )    Positive floating point argument
  978. # #    Integer argument
  979. # <    Non-negative integer argument
  980. # >    Positive integer argument
  981. # specGiven is the option specifier character use, if any (e.g. - or +),
  982. # for use in error messages.
  983. # Returns null on success, err string on error
  984. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  985.     if (ArgType == ":")
  986.     return ""
  987.     if (ArgType == ";") {
  988.     if (Value == "")
  989.         Err = "must be a non-empty string"
  990.     }
  991.     # A number begins with optional + or -, and is followed by a string of
  992.     # digits or a decimal with digits before it, after it, or both
  993.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  994.     Err = "must be a number"
  995.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  996.     Err = "may not include a fraction"
  997.     else if (ArgType ~ "[()<>]" && Value < 0)
  998.     Err = "may not be negative"
  999.     # (
  1000.     else if (ArgType ~ "[)>]" && Value == 0)
  1001.     Err = "must be a positive number"
  1002.     if (Err != "") {
  1003.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  1004.     if (Name != "")
  1005.         return ErrStr "variable " substr(Name,1,1) " " Err
  1006.     else {
  1007.         if (Option == "&")
  1008.         Option = Value
  1009.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  1010.     }
  1011.     }
  1012.     else
  1013.     return ""
  1014. }
  1015.  
  1016. # Note: only the above functions are needed by ProcArgs.
  1017. # The rest of these functions call ProcArgs() and also do other
  1018. # option-processing stuff.
  1019.  
  1020. # Opts: Process command line arguments.
  1021. # Opts processes command line arguments using ProcArgs()
  1022. # and checks for errors.  If an error occurs, a message is printed
  1023. # and the program is exited.
  1024. #
  1025. # Input variables:
  1026. # Name is the name of the program, for error messages.
  1027. # Usage is a usage message, for error messages.
  1028. # OptList the option description string, as used by ProcArgs().
  1029. # MinArgs is the minimum number of non-option arguments that this
  1030. # program should have, non including ARGV[0] and +h.
  1031. # If the program does not require any non-option arguments,
  1032. # MinArgs should be omitted or given as 0.
  1033. # rcFiles, if given, is a colon-seprated list of filenames to read for
  1034. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  1035. # by the value of the environment variable HOME.  If a filename begins with
  1036. # $, the part from the character after the $ up until (but not including)
  1037. # the first character not in [a-zA-Z0-9_] will be searched for in the
  1038. # environment; if found its value will be substituted, if not the filename will
  1039. # be discarded.
  1040. # rcfiles are read in the order given.
  1041. # Values given in them will not override values given on the command line,
  1042. # and values given in later files will not override those set in earlier
  1043. # files, because AssignVal() will store each with a different instance index.
  1044. # The first instance of each variable, either on the command line or in an
  1045. # rcfile, will be stored with no instance index, and this is the value
  1046. # normally used by programs that call this function.
  1047. # VarNames is a comma-separated list of variable names to map to options,
  1048. # in the same order as the options are given in OptList.
  1049. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  1050. # searched for in the environment.  If set to -1, all values will be searched
  1051. # for in the environment.  Values given in the environment will override
  1052. # those given in the rcfiles but not those given on the command line.
  1053. # NoRCopt, if given, is an additional letter option that if given on the
  1054. # command line prevents the rcfiles from being read.
  1055. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  1056. # ExclusiveOptions() for a description of exOpts.
  1057. # Special options:
  1058. # If x is made an option and is given, some debugging info is output.
  1059. # h is assumed to be the help option.
  1060.  
  1061. # Global variables:
  1062. # The command line arguments are taken from ARGV[].
  1063. # The arguments that are option specifiers and values are removed from
  1064. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  1065. # The number of elements in ARGV[] should be in ARGC.
  1066. # After processing, ARGC is set to the number of elements left in ARGV[].
  1067. # The option values are put in Options[].
  1068. # On error, Err is set to a positive integer value so it can be checked for in
  1069. # an END block.
  1070. # Return value: The number of elements left in ARGV is returned.
  1071. # Must keep OptErr global since it may be set by InitOpts().
  1072. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  1073. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  1074.     if (MinArgs == "")
  1075.     MinArgs = 0
  1076.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  1077.     optChars)
  1078.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  1079.     if (ArgsLeft >= 0) {
  1080.         OptErr = "Not enough arguments"
  1081.         Err = 4
  1082.     }
  1083.     else
  1084.         Err = -ArgsLeft
  1085.     printf "%s: %s.\nUse -h for help.\n%s\n",
  1086.     Name,OptErr,Usage > "/dev/stderr"
  1087.     exit 1
  1088.     }
  1089.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  1090.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  1091.     {
  1092.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  1093.     Err = -e
  1094.     exit 1
  1095.     }
  1096.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  1097.     {
  1098.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  1099.     Err = 1
  1100.     exit 1
  1101.     }
  1102.     return ArgsLeft
  1103. }
  1104.  
  1105. # ReadConfFile(): Read a file containing var/value assignments, in the form
  1106. # <variable-name><assignment-char><value>.
  1107. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  1108. # line and whitespace between the variable name and the assignment character) 
  1109. # is stripped.  Lines that do not contain an assignment operator or which
  1110. # contain a null variable name are ignored, other than possibly being noted in
  1111. # the return value.  If more than one assignment is made to a variable, the
  1112. # first assignment is used.
  1113. # Input variables:
  1114. # File is the file to read.
  1115. # Comment is the line-comment character.  If it is found as the first non-
  1116. #     whitespace character on a line, the line is ignored.
  1117. # Assign is the assignment string.  The first instance of Assign on a line
  1118. #     separates the variable name from its value.
  1119. # If StripWhite is true, whitespace around the value (whitespace between the
  1120. #     assignment char and trailing whitespace on the line) is stripped.
  1121. # VarPat is a pattern that variable names must match.  
  1122. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  1123. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  1124. #     a line; no assignment operator is needed.  These variables are set in
  1125. #     the output array with a null value.  Lines containing nothing but
  1126. #     whitespace are still ignored.
  1127. # Output variables:
  1128. # Values[] contains the assignments, with the indexes being the variable names
  1129. #     and the values being the assigned values.
  1130. # Lines[] contains the line number that each variable occured on.  A flag set
  1131. #     is record by giving it an index in Lines[] but not in Values[].
  1132. # Return value:
  1133. # If any errors occur, a string consisting of descriptions of the errors
  1134. # separated by newlines is returned.  In no case will the string start with a
  1135. # numeric value.  If no errors occur,  the number of lines read is returned.
  1136. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  1137. FlagsOK,
  1138. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  1139.     if (Comment != "")
  1140.     Comment = "^" Comment
  1141.     AssignLen = length(Assign)
  1142.     if (VarPat == "")
  1143.     VarPat = "."    # null varname not allowed
  1144.     while ((Status = (getline Line < File)) == 1) {
  1145.     LineNum++
  1146.     sub("^[ \t]+","",Line)
  1147.     if (Line == "")        # blank line
  1148.         continue
  1149.     if (Comment != "" && Line ~ Comment)
  1150.         continue
  1151.     if (Pos = index(Line,Assign)) {
  1152.         Var = substr(Line,1,Pos-1)
  1153.         Val = substr(Line,Pos+AssignLen)
  1154.         if (StripWhite) {
  1155.         sub("^[ \t]+","",Val)
  1156.         sub("[ \t]+$","",Val)
  1157.         }
  1158.     }
  1159.     else {
  1160.         Var = Line    # If no value, var is entire line
  1161.         Val = ""
  1162.     }
  1163.     if (!FlagsOK && Val == "") {
  1164.         Errs = Errs \
  1165.         sprintf("\nBad assignment on line %d of file %s: %s",
  1166.         LineNum,File,Line)
  1167.         continue
  1168.     }
  1169.     sub("[ \t]+$","",Var)
  1170.     if (Var !~ VarPat) {
  1171.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  1172.         LineNum,File,Var)
  1173.         continue
  1174.     }
  1175.     if (!(Var in Lines)) {
  1176.         Lines[Var] = LineNum
  1177.         if (Pos)
  1178.         Values[Var] = Val
  1179.     }
  1180.     }
  1181.     if (Status)
  1182.     Errs = Errs "\nCould not read file " File
  1183.     close(File)
  1184.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  1185. }
  1186.  
  1187. # Variables:
  1188. # Data is stored in Options[].
  1189. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  1190. # Global vars:
  1191. # Sets OptErr.  Uses ENVIRON[].
  1192. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  1193. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  1194. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  1195. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  1196.     split("",filesRead,"")    # make awk know this is an array
  1197.     NumVars = split(VarNames,Vars,",")
  1198.     TypesInd = Ret = 0
  1199.     if (EnvSearch == -1)
  1200.     EnvSearch = NumVars
  1201.     for (i = 1; i <= NumVars; i++) {
  1202.     Var = Vars[i]
  1203.     CharOpt = substr(OptList,++TypesInd,1)
  1204.     if (CharOpt ~ "^[:;*()#<>&]$")
  1205.         CharOpt = substr(OptList,++TypesInd,1)
  1206.     Map[Var] = CharOpt
  1207.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1208.     # Do not overwrite entries from environment
  1209.     if (i <= EnvSearch && Var in ENVIRON &&
  1210.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  1211.         return Err
  1212.     }
  1213.  
  1214.     numrcFiles = split(rcFiles,fNames,":")
  1215.     for (i = 1; i <= numrcFiles; i++) {
  1216.     rcFile = fNames[i]
  1217.     if (rcFile ~ "^~/")
  1218.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1219.     else if (rcFile ~ /^\$/) {
  1220.         rcFile = substr(rcFile,2)
  1221.         match(rcFile,"^[a-zA-Z0-9_]*")
  1222.         envvar = substr(rcFile,1,RLENGTH)
  1223.         if (envvar in ENVIRON)
  1224.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1225.         else
  1226.         continue
  1227.     }
  1228.     if (rcFile in filesRead)
  1229.         continue
  1230.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1231.     # may be the same
  1232.     filesRead[rcFile]
  1233.     if ("x" in Options)
  1234.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1235.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1236.     if (retStr > 0)
  1237.         READ_RCFILE = 1
  1238.     else if (ret != "") {
  1239.         OptErr = retStr
  1240.         Ret = -1
  1241.     }
  1242.     for (Var in Lines)
  1243.         if (Var in Map) {
  1244.         if ((Err = AssignVal(Map[Var],
  1245.         Var in Values ? Values[Var] : "",Options,Types[Var],
  1246.         Var in Values,Var,0)) < 0)
  1247.             return Err
  1248.         }
  1249.         else {
  1250.         OptErr = sprintf(\
  1251.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1252.         Lines[Var],rcFile)
  1253.         Ret = -1
  1254.         }
  1255.     }
  1256.  
  1257.     if ("x" in Options)
  1258.     for (Var in Map)
  1259.         if (Map[Var] in Options)
  1260.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1261.         "/dev/stderr"
  1262.         else
  1263.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1264.     return Ret
  1265. }
  1266.  
  1267. # OptSets is a semicolon-separated list of sets of option sets.
  1268. # Within a list of option sets, the option sets are separated by commas.  For
  1269. # each set of sets, if any option in one of the sets is in Options[] AND any
  1270. # option in one of the other sets is in Options[], an error string is returned.
  1271. # If no conflicts are found, nothing is returned.
  1272. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1273. # the exclusions presented by the first set of sets (ab,def,g) if:
  1274. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1275. # (a or b is in Options[]) AND (g is in Options) OR
  1276. # (d, e, or f is in Options[]) AND (g is in Options)
  1277. # An error will be returned due to the exclusions presented by the second set
  1278. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1279. # todo: make options given on command line unset options given in config file
  1280. # todo: that they conflict with.
  1281. function ExclusiveOptions(OptSets,Options,
  1282. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1283. SetNum,OSetNum) {
  1284.     NumSetSets = split(OptSets,SetSets,";")
  1285.     # For each set of sets...
  1286.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1287.     # NumSets is the number of sets in this set of sets.
  1288.     NumSets = split(SetSets[SetSet],Sets,",")
  1289.     # For each set in a set of sets except the last...
  1290.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1291.         s1 = Sets[SetNum]
  1292.         L1 = length(s1)
  1293.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1294.         # If any of the options in this set was given, check whether
  1295.         # any of the options in the other sets was given.  Only check
  1296.         # later sets since earlier sets will have already been checked
  1297.         # against this set.
  1298.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1299.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1300.             s2 = Sets[OSetNum]
  1301.             L2 = length(s2)
  1302.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1303.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1304.                 ErrStr = ErrStr "\n"\
  1305.                 sprintf("Cannot give both %s and %s options.",
  1306.                 c1,c2)
  1307.             }
  1308.     }
  1309.     }
  1310.     if (ErrStr != "")
  1311.     return substr(ErrStr,2)
  1312.     return ""
  1313. }
  1314.  
  1315. # The value of each instance of option Opt that occurs in Options[] is made an
  1316. # index of Set[].
  1317. # The return value is the number of instances of Opt in Options.
  1318. function Opt2Set(Options,Opt,Set,  count) {
  1319.     if (!(Opt in Options))
  1320.     return 0
  1321.     Set[Options[Opt]]
  1322.     count = Options[Opt,"count"]
  1323.     for (; count > 1; count--)
  1324.     Set[Options[Opt,count]]
  1325.     return count
  1326. }
  1327.  
  1328. # The value of each instance of option Opt that occurs in Options[] that
  1329. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1330. # Other values are made indexes of Set[].
  1331. # The return value is the number of instances of Opt in Options.
  1332. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1333.     ret = Opt2Set(Options,Opt,aSet)
  1334.     for (value in aSet)
  1335.     if (substr(value,1,1) == "!")
  1336.         nSet[substr(value,2)]
  1337.     else
  1338.         Set[value]
  1339.     return ret
  1340. }
  1341.  
  1342. # Returns true if option Opt was given on the command line.
  1343. function CmdLineOpt(Options,Opt,  i) {
  1344.     for (i = 1; (Opt,"num",i) in Options; i++)
  1345.     if (Options[Opt,"num",i] != 0)
  1346.         return 1
  1347.     return 0
  1348. }
  1349. ### End of ProcArgs library
  1350. ### Begin array routines
  1351.  
  1352. # InitArr: Initialize an array with values.
  1353. # Ind and Vals are separated into lists on Sep.
  1354. # For each item in Ind, an index with that name is created in Arr[],
  1355. # and the value with the same position in Vals is stored in it.
  1356. # Global variables: none.
  1357. function InitArr(Arr,Ind,Vals,sep,  numind,indnames,values) {
  1358.     split(Ind,indnames,sep)
  1359.     split(Vals,values,sep)
  1360.     for (numind in indnames)
  1361.     Arr[indnames[numind]] = values[numind]
  1362. }
  1363.  
  1364. function ClearArr(Arr,  Elem) {
  1365.     for (Elem in Arr)
  1366.     delete Arr[Elem]
  1367. }
  1368.  
  1369. function CopyArr(From,To,  Elem) {
  1370.     for (Elem in From)
  1371.     To[Elem] = From[Elem]
  1372. }
  1373.  
  1374. # Subtract the values in Subtrahend from those in Minuend
  1375. function SubtractArr(Minuend,Subtrahend,  Elem) {
  1376.     for (Elem in Subtrahend)
  1377.     Minuend[Elem] -= Subtrahend[Elem]
  1378. }
  1379. # For each element of the array In, an element is created in Out having
  1380. # an index equal to the value of the element in In and a value equal to
  1381. # the index of the element in In.
  1382. function Invert(In,Out,  Index) {
  1383.     for (Index in In)
  1384.     Out[In[Index]] = Index
  1385. }
  1386.  
  1387. # Assign: make an array from a list of assignments.
  1388. # An index with the name of each variable in the list is created in the array.
  1389. # Its value is set to the value given for it.
  1390. # Input variables:
  1391. # Elements is a string containing the list of variable-value pairs.
  1392. # Sep is the string that separates the pairs in the list.
  1393. # AssignOp is the string that separates variables from values.
  1394. # Output variables:
  1395. # Arr is the array.
  1396. # Return value: the number of elements added to the set.
  1397. # Example:
  1398. # Assign(Arr,"foo=blot bar=blat baz=blit"," ","=")
  1399. function Assign(Arr,Elements,Sep,AssignOp,
  1400. Num,Names,Elem,Assignments,Assignment,i) {
  1401.     Num = split(Elements,Assignments,Sep)
  1402.     for (i = 1; i <= Num; i++) {
  1403.     Assignment = Assignments[i]
  1404.     Ind = index(Assignment,AssignOp)
  1405.     Arr[substr(Assignment,1,Ind - 1)] = substr(Assignment,Ind + 1)
  1406.     }
  1407.     return Num
  1408. }
  1409.  
  1410. # Packs Arr[], which should have integer indices starting at or above n, to
  1411. # contiguous integer indices starting with n.
  1412. # If n is not given it defaults to 0.
  1413. # Num should be the number of elements in Arr.
  1414. function PackArr(Arr,Num,n,  NewInd,OldInd) {
  1415.     NewInd = OldInd = n+0
  1416.     for (; Num; Num--) {
  1417.     while (!(OldInd in Arr))
  1418.         OldInd++
  1419.     if (NewInd != OldInd) {
  1420.         Arr[NewInd] = Arr[OldInd]
  1421.         delete Arr[OldInd]
  1422.     }
  1423.     OldInd++
  1424.     NewInd++
  1425.     }
  1426. }
  1427. ### End array routines
  1428.